package zaplogger

import (
	"context"
	"errors"
	"go.uber.org/zap"
	"gorm.io/gorm"
	gormLogger "gorm.io/gorm/logger"
	"time"
)

// GormLogger 是用 Zap 自定义 Gorm 框架使用的日志记录器
type GormLogger struct {
	ZapLogger                 *zap.Logger         // zap.Logger 对象的指针
	LogLevel                  gormLogger.LogLevel // 日志级别
	SlowThreshold             time.Duration       // 慢 SQL 周期
	SkipCallerLookup          bool                // 是否跳过函数调用查找
	IgnoreRecordNotFoundError bool                // 是否忽略 RecordNotFoundError 错误
}

// NewGormLogger 创建一个 GormLogger
// @parameter zapLogger zap.Logger 对象的指针
// @return    *GormLogger
func NewGormLogger(zapLogger *zap.Logger) *GormLogger {
	return &GormLogger{
		ZapLogger:                 zapLogger,              // zap.Logger 对象的指针
		LogLevel:                  gormLogger.Info,        // 日志级别
		SlowThreshold:             time.Millisecond * 100, // 慢 SQL 周期
		SkipCallerLookup:          false,                  // 是否跳过函数调用查找
		IgnoreRecordNotFoundError: false,                  // 是否忽略 RecordNotFoundError 错误
	}
}

// SetAsDefault 将当前自定义的 GormLogger 作为 Gorm 框架的默认值
func (l GormLogger) SetAsDefault() {
	gormLogger.Default = l
}

// LogMode 设置日志记录模式
// @parameter level 日志级别
// @return    gormLogger.Interface 返回 gorm 内部定义的 Interface 接口类型
func (l GormLogger) LogMode(level gormLogger.LogLevel) gormLogger.Interface {
	return GormLogger{
		ZapLogger:                 l.ZapLogger,
		SlowThreshold:             l.SlowThreshold,
		LogLevel:                  level,
		SkipCallerLookup:          l.SkipCallerLookup,
		IgnoreRecordNotFoundError: l.IgnoreRecordNotFoundError,
	}
}

// Info 输出 Info 级别的日志
// @parameter ctx 上下文对象
// @parameter msg 输出的日志文本
// @parameter args
func (l GormLogger) Info(ctx context.Context, msg string, args ...interface{}) {
	if l.LogLevel < gormLogger.Info {
		return
	}
	l.ZapLogger.Sugar().Debugf(msg, args...)
}

// Warn 输出 Warn 级别的日志
// @parameter ctx 上下文对象, 这里没有使用
// @parameter msg 输出的日志文本
// @parameter args 其他附加消息
func (l GormLogger) Warn(ctx context.Context, msg string, args ...interface{}) {
	if l.LogLevel < gormLogger.Warn {
		return
	}
	l.ZapLogger.Sugar().Warnf(msg, args...)
}

// Error
// @parameter ctx 上下文对象, 这里没有使用
// @parameter str 要输出的消息文本消息
// @parameter args 其他附加消息
func (l GormLogger) Error(ctx context.Context, msg string, args ...interface{}) {
	if l.LogLevel < gormLogger.Error {
		return
	}
	l.ZapLogger.Sugar().Errorf(msg, args...)
}

// Trace
// @parameter ctx 上下文对象, 这里没有使用
// @parameter begin 开始时间
// @parameter fc 回调函数
// @parameter err 错误信息
func (l GormLogger) Trace(ctx context.Context, begin time.Time, fc func() (sql string, rowsAffected int64), err error) {
	if l.LogLevel <= 0 {
		return
	}
	// 计算与开始时间的时间差, 也就是是此次 Gorm 数据库操作所耗费的时间
	elapsed := time.Since(begin)
	switch {
	case err != nil && l.LogLevel >= gormLogger.Error && (!l.IgnoreRecordNotFoundError || !errors.Is(err, gorm.ErrRecordNotFound)): // 错误日志输出
		sql, rows := fc()
		l.ZapLogger.Error("trace",
			zap.Error(err),
			zap.Duration("elapsedTime", elapsed),
			zap.Int64("rowsAffected", rows),
			zap.String("sql", sql))

	case l.SlowThreshold != 0 && elapsed > l.SlowThreshold && l.LogLevel >= gormLogger.Warn: // 警告日志输出
		sql, rows := fc()
		l.ZapLogger.Warn("trace",
			zap.Duration("elapsedTime", elapsed),
			zap.Int64("rowsAffected", rows),
			zap.String("sql", sql))

	case l.LogLevel >= gormLogger.Info: // Info 日志数据
		sql, rows := fc()
		l.ZapLogger.Debug("trace",
			zap.Duration("elapsedTime", elapsed),
			zap.Int64("rowsAffected", rows),
			zap.String("sql", sql))
	}
}
